﻿using System;
using Nefarius.ViGEm.Client.Targets;
using Nefarius.ViGEm.Client.Targets.DualShock4;

namespace BetterJoyForCemu.Controller {
    public enum DpadDirection {
        None,
        Northwest,
        West,
        Southwest,
        South,
        Southeast,
        East,
        Northeast,
        North,
    }

    public struct OutputControllerDualShock4InputState {
        public bool triangle;
        public bool circle;
        public bool cross;
        public bool square;

        public bool trigger_left;
        public bool trigger_right;

        public bool shoulder_left;
        public bool shoulder_right;

        public bool options;
        public bool share;
        public bool ps;
        public bool touchpad;

        public bool thumb_left;
        public bool thumb_right;

        public DpadDirection dPad;

        public byte thumb_left_x;
        public byte thumb_left_y;
        public byte thumb_right_x;
        public byte thumb_right_y;

        public byte trigger_left_value;
        public byte trigger_right_value;

        public bool IsEqual(OutputControllerDualShock4InputState other) {
            bool buttons = triangle == other.triangle
                && circle == other.circle
                && cross == other.cross
                && square == other.square
                && trigger_left == other.trigger_left
                && trigger_right == other.trigger_right
                && shoulder_left == other.shoulder_left
                && shoulder_right == other.shoulder_right
                && options == other.options
                && share == other.share
                && ps == other.ps
                && touchpad == other.touchpad
                && thumb_left == other.thumb_left
                && thumb_right == other.thumb_right
                && dPad == other.dPad;

            bool axis = thumb_left_x == other.thumb_left_x
                && thumb_left_y == other.thumb_left_y
                && thumb_right_x == other.thumb_right_x
                && thumb_right_y == other.thumb_right_y;

            bool triggers = trigger_left_value == other.trigger_left_value
                && trigger_right_value == other.trigger_right_value;

            return buttons && axis && triggers;
        }
    }

    public class OutputControllerDualShock4 {
        private IDualShock4Controller controller;

        private OutputControllerDualShock4InputState current_state;


        public delegate void DualShock4FeedbackReceivedEventHandler(DualShock4FeedbackReceivedEventArgs e);
        public event DualShock4FeedbackReceivedEventHandler FeedbackReceived;

        public OutputControllerDualShock4() {
            controller = Program.emClient.CreateDualShock4Controller();
            Init();
        }

        public OutputControllerDualShock4(ushort vendor_id, ushort product_id) {
            controller = Program.emClient.CreateDualShock4Controller(vendor_id, product_id);
            Init();
        }

        private void Init() {
            controller.AutoSubmitReport = false;
            controller.FeedbackReceived += FeedbackReceivedRcv;
        }

        private void FeedbackReceivedRcv(object _sender, DualShock4FeedbackReceivedEventArgs e) {
            FeedbackReceived(e);
        }

        public void Connect() {
            controller.Connect();
        }

        public void Disconnect() {
            controller.Disconnect();
        }

        public bool UpdateInput(OutputControllerDualShock4InputState new_state) {
            if (current_state.IsEqual(new_state)) {
                return false;
            }

            DoUpdateInput(new_state);

            return true;
        }

        private void DoUpdateInput(OutputControllerDualShock4InputState new_state) {
            controller.SetButtonState(DualShock4Button.Triangle, new_state.triangle);
            controller.SetButtonState(DualShock4Button.Circle, new_state.circle);
            controller.SetButtonState(DualShock4Button.Cross, new_state.cross);
            controller.SetButtonState(DualShock4Button.Square, new_state.square);

            controller.SetButtonState(DualShock4Button.ShoulderLeft, new_state.shoulder_left);
            controller.SetButtonState(DualShock4Button.ShoulderRight, new_state.shoulder_right);

            controller.SetButtonState(DualShock4Button.TriggerLeft, new_state.trigger_left);
            controller.SetButtonState(DualShock4Button.TriggerRight, new_state.trigger_right);

            controller.SetButtonState(DualShock4Button.ThumbLeft, new_state.thumb_left);
            controller.SetButtonState(DualShock4Button.ThumbRight, new_state.thumb_right);

            controller.SetButtonState(DualShock4Button.Share, new_state.share);
            controller.SetButtonState(DualShock4Button.Options, new_state.options);
            controller.SetButtonState(DualShock4SpecialButton.Ps, new_state.ps);
            controller.SetButtonState(DualShock4SpecialButton.Touchpad, new_state.touchpad);

            controller.SetDPadDirection(MapDPadDirection(new_state.dPad));

            controller.SetAxisValue(DualShock4Axis.LeftThumbX, new_state.thumb_left_x);
            controller.SetAxisValue(DualShock4Axis.LeftThumbY, new_state.thumb_left_y);
            controller.SetAxisValue(DualShock4Axis.RightThumbX, new_state.thumb_right_x);
            controller.SetAxisValue(DualShock4Axis.RightThumbY, new_state.thumb_right_y);

            controller.SetSliderValue(DualShock4Slider.LeftTrigger, new_state.trigger_left_value);
            controller.SetSliderValue(DualShock4Slider.RightTrigger, new_state.trigger_right_value);

            controller.SubmitReport();

            current_state = new_state;
        }

        private DualShock4DPadDirection MapDPadDirection(DpadDirection dPad) {
            switch (dPad) {
                case DpadDirection.None: return DualShock4DPadDirection.None;
                case DpadDirection.North: return DualShock4DPadDirection.North;
                case DpadDirection.Northeast: return DualShock4DPadDirection.Northeast;
                case DpadDirection.East: return DualShock4DPadDirection.East;
                case DpadDirection.Southeast: return DualShock4DPadDirection.Southeast;
                case DpadDirection.South: return DualShock4DPadDirection.South;
                case DpadDirection.Southwest: return DualShock4DPadDirection.Southwest;
                case DpadDirection.West: return DualShock4DPadDirection.West;
                case DpadDirection.Northwest: return DualShock4DPadDirection.Northwest;
                default: throw new NotImplementedException();
            }
        }
    }
}
